/**
 * 
 */
package ro.dta.idbi.patterns;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.model.AbstractPattern;
import ro.dta.idbi.model.AutoMedUtils;
import ro.dta.idbi.model.Utils;
import uk.ac.ic.doc.automed.IntegrityException;
import uk.ac.ic.doc.automed.NotFoundException;
import uk.ac.ic.doc.automed.modelmanagement.modeldef.SQLModelDef;
import uk.ac.ic.doc.automed.reps.Schema;
import uk.ac.ic.doc.automed.reps.SchemaObject;

/**
 * <b>Addition of many-to-many relationship</b><br />
 * 
 * Example of use:<br />
 * 
 * <code>add_mtm_rel (&lt;&lt;table1&gt;&gt;, &lt;&lt;table2&gt;&gt;, &lt;&lt;new_table&gt;&gt;)</code>
 * <br />
 * 
 * A foreign key constraint from <i>table1,col1</i> to <i>table2,col2</i> is
 * introduced
 * 
 * @author Tudor Dobrila
 * 
 */
public class AddRelationMTM extends AbstractPattern {

	/**
	 * Name of the pattern
	 */
	public static final String NAME = "Addition of Many-To-Many Relationship";

	/**
	 * Description of the pattern
	 */
	public static final String DESCRIPTION = "Introduce a many-to-many relationship";

	/**
	 * Name of the command associated with the pattern
	 */
	public static final String COMMAND = "add_mtm_rel";

	/**
	 * Integration phase during which the command is applied
	 */
	public static final EnumSet<IntegrationOperation> OPERATION = EnumSet
			.of(IntegrationOperation.MERGING);

	/**
	 * Name of the first table in the relationship
	 */
	private String table1;

	/**
	 * Name of the second table in the relationship
	 */
	private String table2;

	/**
	 * Name of the new table being introduced to represent the relationship
	 */
	private String newTable;

	/**
	 * Addition of many-to-many relationship pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Source schema over which the transformation is applied
	 */
	public AddRelationMTM(final Schema sourceSchema) throws NotFoundException, IntegrityException {
		super(sourceSchema);

		pattern = Pattern.compile("^" + COMMAND + "[ ]*\\((" + TABLE_REGEX + ")" + COMMA_REGEX
				+ "(" + TABLE_REGEX + ")" + COMMA_REGEX + "(" + TABLE_REGEX + ")\\)$");
	}

	/**
	 * Addition of many-to-many relationship pattern class constructor
	 * 
	 * @param sourceSchema
	 *            Name of source schema over which the transformation is applied
	 */
	public AddRelationMTM(final String sourceSchema) throws NotFoundException, IntegrityException {
		this(Schema.getSchema(sourceSchema));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.AbstractPattern#parse(java.lang.String)
	 */
	@Override
	protected void parse(String trans) {
		final Matcher matcher = pattern.matcher(trans);
		matcher.find();

		table1 = parseTableName(matcher.group(1));
		table2 = parseTableName(matcher.group(2));
		newTable = parseTableName(matcher.group(3));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.AbstractPattern#getPrimitiveCommands()
	 */
	@Override
	protected List<String> getPrimitiveCommands() throws NotFoundException, IntegrityException {
		List<String> result = new ArrayList<String>();
		StringBuilder cmd = new StringBuilder();

		SchemaObject table1Obj = sourceSchema.getSchemaObject("<<" + table1 + ">>");
		SchemaObject table2Obj = sourceSchema.getSchemaObject("<<" + table2 + ">>");
		List<String> pks1 = AutoMedUtils.getPK(sourceSchema, table1Obj);
		List<String> pks2 = AutoMedUtils.getPK(sourceSchema, table2Obj);

		// Step 1: create the new table
		cmd.append("extend (");
		cmd.append(Utils.genTableRepresentation(newTable));
		cmd.append(", Range Void Any)");
		result.add(cmd.toString());

		// Step 2: create the PK columns in the new table
		List<String> sCols1 = new ArrayList<String>();
		List<String> sCols2 = new ArrayList<String>();
		List<String> colsName1 = new ArrayList<String>();
		List<String> colsName2 = new ArrayList<String>();
		result.addAll(genPKCommands(table1, pks1, sCols1, colsName1));
		result.addAll(genPKCommands(table2, pks2, sCols2, colsName2));
		Object[] sColArr1 = sCols1.toArray();
		Object[] sColArr2 = sCols2.toArray();
		Object[] pkArr = new Object[sColArr1.length + sColArr2.length + 2];
		pkArr[0] = newTable + "_pk";
		pkArr[1] = newTable;
		System.arraycopy(sColArr1, 0, pkArr, 2, sColArr1.length);
		System.arraycopy(sColArr2, 0, pkArr, 2 + sColArr1.length, sColArr2.length);

		// Step 3: create the PK constraint for the new table
		if (sqlModel.isFeatureInUse(SQLModelDef.PRIMARY_KEY)) {
			cmd.setLength(0);
			cmd.append("add (").append(Utils.genPKRepresentation(pkArr)).append(")");
			result.add(cmd.toString());
		}

		// Step 4: create the FK constraints from the new table to the original
		// tables
		if (sqlModel.isFeatureInUse(SQLModelDef.FOREIGN_KEY)) {
			cmd.setLength(0);
			cmd.append("add (")
					.append(Utils.genFKRepresentation(newTable, table1, colsName1, pks1));
			cmd.append(")");
			result.add(cmd.toString());
			cmd.setLength(0);
			cmd.append("add (")
					.append(Utils.genFKRepresentation(newTable, table2, colsName2, pks2));
			cmd.append(")");
			result.add(cmd.toString());
		}

		return result;
	}

	/**
	 * Generate the extend of the PK columns commands
	 * 
	 * @param table
	 *            Table for which the PK columns are considered
	 * @param pks
	 *            Primary key columns of the table <i>table</i>
	 * @param sourceCols
	 *            List that will be filled with the AutoMed representation of
	 *            the columns being added
	 * @param colsName
	 *            List that will be filled with the names of the columns added
	 * @return List of extend commands
	 */
	private List<String> genPKCommands(String table, List<String> pks, List<String> sourceCols,
			List<String> colsName) throws NotFoundException {
		List<String> result = new ArrayList<String>();
		StringBuilder cmd = new StringBuilder();

		for (String pk : pks) {
			SchemaObject pkObj = sourceSchema.getSchemaObject("<<" + table + "," + pk + ">>");
			Object[] pkDef = pkObj.getSchemeDefinition();
			cmd.setLength(0);
			cmd.append("extend (");
			String colName = table + "_" + pkDef[1];
			cmd.append(Utils.genColRepresentation(newTable, colName, pkDef[2], pkDef[3]));
			cmd.append(", Range Void Any)");
			result.add(cmd.toString());
			sourceCols.add("<<" + newTable + "," + colName + ">>");
			colsName.add(colName);
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ro.dta.idbi.model.AbstractPattern#verify()
	 */
	@Override
	protected boolean verify() {
		// TODO Auto-generated method stub
		return true;
	}

}
